package tacos;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.StreamSupport;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.SessionAttributes;

import  lombok.extern.slf4j.Slf4j;
import tacos.data.IngredientRepository;

import javax.validation.Valid;

@Slf4j // @Slf4j，这是Lombok所提供的注解，在编译期，它会在这个类中自动生成一个SLF4J Logger（SLF4J即simple logging facade for Java）静态属性。
@Controller
@RequestMapping("/design")
// @SessionAttributes("tacoOrder")注解，这表明在这个类中稍后放到模型里面的TacoOrder对象应该在会话中一直保持。
// 这一点非常重要，因为创建taco也是创建订单的第一步，而我们创建的订单需要在会话中保存，这样能够使其跨多个请求。
@SessionAttributes("tacoOrder")
public class DesignTacoController {

    private final IngredientRepository ingredientRepo;

    @Autowired
    public DesignTacoController(IngredientRepository ingredientRepo) {
        this.ingredientRepo = ingredientRepo;
    }

    // `@ModelAttribute` 是 Spring MVC 中的一个重要注解，主要用于将请求参数绑定到模型对象以及将模型对象中的数据暴露给视图页面。
    // Model对象负责在控制器和展现数据的视图之间传递数据。实际上，放到Model属性中的数据将会复制到Servlet Request的属性中，
    // 这样视图就能找到它们，并使用它们在用户的浏览器中渲染页面。
    @ModelAttribute
    public void addIngradientsToModel(Model model) {
        Iterable<Ingredient> ingredientsInMethod = this.ingredientRepo.findAll();

        Ingredient.Type[] types = Ingredient.Type.values();
        for (Ingredient.Type type : types) {
            model.addAttribute(type.toString().toLowerCase(), filterByType(ingredientsInMethod, type));
        }
    }

    // TacoOrder对象在前面阐述@SessionAttributes注解的时候曾经提到过，当用户在多个请求之间创建taco时，它会持有正在建立的订单的状态。
    @ModelAttribute(name = "tacoOrder")
    public TacoOrder order() {
        return new TacoOrder();
    }

    // Taco对象也被放置到了模型中，这样一来，为响应“/design”的GET请求而呈现的视图就能展示一个非空的对象了。
    @ModelAttribute(name = "taco")
    public Taco taco(){
        return new Taco();
    }

    @GetMapping
    public String showDesignForm(){
        // 返回design.html视图逻辑名称
        return "design";
    }

    /*
    * TacoOrder参数上所使用的@ModelAttribute表明它应该使用模型中的TacoOrder对象，
    * 这个对象是我们在前面的程序清单2.4中借助带有@ModelAttribute注解的order()方法放到模型中的。
    * */
    @PostMapping
    public String processTaco(@Valid Taco taco, Errors errors, @ModelAttribute TacoOrder tacoOrder) {
        /*
        * 像["FLTO", "GRBF", "LETC"]这样的文本列表该如何绑定到一个Ingredient对象的列表上呢？要知道，Ingredient是一个更丰富的类型，不仅包括ID，还包括一个描述性的名字和配料类型。
        * 这就是转换器(converter)的用武之地了。转换器是实现了Spring的Converter接口并实现了convert()方法的类，该方法会接收一个值并将其转换成另外一个值。
        * */

        // @Valid注解会告诉Spring MVC要对提交的Taco对象进行校验，而校验时机是在它绑定完表单数据之后、调用processDesign()之前。
        if (errors.hasErrors()) {
            return "design";
        }

        tacoOrder.addTaco(taco);
        log.info("Processed taco: {}", taco);
        // 返回的值带有“redirect:”前缀，表明这是一个重定向视图。
        return "redirect:/orders/current";
    }

    /*
    * `java.util.stream.Collectors` 是 Java 8 引入的一个工具类，它提供了一系列的静态方法用于生成各种 Collector 实例。
    * Collector 在流（Stream）API中用于将流中的元素进行组合、分组、聚合等操作，最终产生一个汇总结果。
    * Collectors 类包含了许多实用的方法来帮助你完成这些任务。
    * `Collectors.toList()` 是 `Collectors` 类提供的一个静态方法，其作用是将流中的元素收集到一个新的 `List` 集合中。
    * 这是一个非常常用的操作，特别是在需要将流处理的结果保存或传递给其他部分时。
    * 以下是 `Collectors.toList()` 的基本使用示例：
```java
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Example {
    public static void main(String[] args) {
        List<String> strings = Arrays.asList("apple", "banana", "orange");

        // 使用 stream 过滤出长度大于5的字符串，并将其收集为列表
        List<String> filteredStrings = strings.stream()
                                              .filter(s -> s.length() > 5)
                                              .collect(Collectors.toList());

        System.out.println(filteredStrings); // 输出: [banana, orange]
    }
}
```
    * 在这个例子中，我们首先创建了一个字符串列表，然后通过流过滤掉那些长度不大于5的字符串，并使用 `Collectors.toList()` 方法将剩余的元素收集到一个新的列表中。
    * 除了 `toList()`，`Collectors` 类还提供了许多其他有用的方法，例如 `toSet()`、`toMap()`、`groupingBy()` 等，可以满足不同场景下的数据收集需求。
    * 这些方法极大地增强了处理集合和流的能力，使得代码更加简洁和易读。
* */
    private Iterable<Ingredient> filterByType(Iterable<Ingredient> ingredients, Ingredient.Type type) {
        // 使用 Stream API 来过滤指定类型的配料
        return StreamSupport.stream(ingredients.spliterator(), false)
                .filter(ingredient -> ingredient.getType() == type)
                .collect(Collectors.toList());
    }

    /*
     * 如果现在运行应用并在浏览器上访问“/design”路径，DesignTacoController的showDesignForm()和addIngredientsToModel()方法将会被调用，
     * 它们在将请求传递给视图之前，会将配料和一个空的Taco放到模型中。但是，我们现在还没有定义视图，请求将会遇到很糟糕的问题，也就是HTTP 500 (Internal Server Error)错误。
     * */
}
